/**
 * @file useField
 * @author  fanxl
 */
import {computed, unref, reactive, toRefs} from 'vue';
import type {ComputedRef} from 'vue';
import {get, isBoolean, isObject} from 'lodash-es';
import {useFormInject} from './useForm';
import type {NestedObjectTypeValue, UseField} from './types';
import {getTouched} from './utils';
import {useExpression} from './useExpression';
import type {LikeFieldType} from '../interface/field';

export function useField(fieldProps: ComputedRef<LikeFieldType> | LikeFieldType): UseField {
    if (!unref(fieldProps) || !unref(fieldProps).name) {
        throw new Error('name is necessary');
    }
    const form = useFormInject();
    const {
        getFieldValue,
        getListChars,
        setFieldValue,
        setFieldTouched,
        getFieldTouched,
        getFieldError,
        getFieldRegistry,
        setInlineState,
        setInlineError,
        $EventBus
    } = form;
    const fieldName = computed(() => unref(fieldProps).path);

    const value = computed(() => getFieldValue(unref(fieldName)));
    const chars = computed(() => getListChars(unref(fieldName)) || []);
    const initialValue = computed(() => get(form.initialValues, unref(fieldName)));
    const errors = computed(() => getFieldError(unref(fieldName)));
    const computedTouched = computed(() => {
        const touched = getFieldTouched(unref(fieldName));
        if (isBoolean(touched)) {
            return touched;
        }
        if (isObject(touched)) {
            return getTouched(touched as NestedObjectTypeValue<boolean>);
        }
        return false;
    });
    const handleChange = (val: any) => {
        setFieldValue(unref(fieldName), val);
        if (!unref(computedTouched)) {
            setFieldTouched(unref(fieldName), true, false);
        }
    };

    const setInlineMessage = (messageType: string, extName: string, value: Array<string>) => {
        if (!messageType || !extName) {
            throw new Error('入参必须传入两级路径');
        }
        setInlineState([unref(fieldName), messageType, extName], value);
    };

    const handleBlur = () => {
        const shouldValidate = !unref(computedTouched);
        setFieldTouched(unref(fieldName), true, shouldValidate);
    };
    const expression = useExpression(fieldProps, fieldName);
    const refExpression = toRefs(expression);
    const formField = computed(() => getFieldRegistry(unref(fieldName)));

    const field = reactive({
        fieldProps,
        fieldName,
        value,
        chars,
        initialValue,
        errors,
        touched: computedTouched,
        formField,
        $EventBus,
        isRemove: refExpression.isRemove,
        isHidden: refExpression.isHidden,

        onChange: handleChange,
        onBlur: handleBlur,

        setValue: handleChange,
        setInlineMessage
    });

    const setValue = (value: any, shouldValidate?: boolean, ifUpdateCharsAboutList?: boolean) => {
        setFieldValue(unref(fieldName), value, {
            shouldValidate,
            ifUpdateCharsAboutList
        });
    };
    const setTouched = (value: NestedObjectTypeValue<boolean>, shouldValidate?: boolean) => {
        setFieldTouched(unref(fieldName), value, shouldValidate);
    };
    const setError = (key: string, value: string[]) => {
        setInlineError(unref(fieldName), key, value);
    };

    const removeError = (key: string) => {
        setInlineError(unref(fieldName), key, []);
    };

    const helper = reactive({
        setValue,
        setTouched,
        setError,
        removeError
    });

    return {
        field,
        helper
    };
}
